// types.hpp : Defines basic types (vectors, ray, color), and operations on it.
//

#ifndef TYPES_HPP_INCLUDE
#define TYPES_HPP_INCLUDE

#include <iostream>
#include <vector>
#include <cmath>
#include <memory>

using namespace std;

struct Vector3;
struct Rotations;
struct Scales;
struct Matrix3;
struct Matrix4;
struct Ray;
class Color;

double dot(const Vector3& v1, const Vector3& v2);
double mod(const Vector3& v);
Vector3 normalize(const Vector3& v);
double determinant(const Matrix3& m);
Matrix3 inverse(const Matrix3& m);
ostream& operator<<(ostream& os, const Matrix3& dt);
Matrix4 translateMatrix(const Vector3 & translates);
Matrix4 translateMatrix(double x, double y, double z);
Matrix4 translateMatrix(const double xyz[3]);
Matrix3 scaleMatrix(const Vector3 & scales);
Matrix3 scaleMatrix(double x, double y, double z);
Matrix3 scaleMatrix(const double xyz[3]);
Matrix3 headingMatrix(double angle); // about y
Matrix3 pitchMatrix(double angle);   // about z
Matrix3 bankMatrix(double angle);    // about x
Matrix3 rotationMatrix(Vector3 hpb);
Matrix3 rotationMatrix(double heading, double pitch, double bank);
Matrix3 rotationMatrix(const double hpb[3]);
ostream& operator<<(ostream& os, const Ray& dt);
ostream& operator<<(ostream& os, const Color& dt);

struct Vector3
{
	union
	{
		double xyz[3];
		struct
		{
			double x, y, z;
		};
	};

	Vector3() {}
	Vector3(double xyz) : x(xyz), y(xyz), z(xyz) {}
	Vector3(double x, double y, double z);
	Vector3(const double xyz[3]) : x(xyz[0]), y(xyz[1]), z(xyz[2]) {}
	Vector3(const float xyz[3]) : x(xyz[0]), y(xyz[1]), z(xyz[2]) {}
	friend ostream& operator<<(ostream& os, const Vector3& dt);

/*	double getX() const;
	double getY() const;
	double getZ() const;
	void setX(double x);
	void setY(double Y);
	void setZ(double Z);*/

	Vector3 operator+(const Vector3& arg) const;
	Vector3 operator-(const Vector3& arg) const;
	Vector3 operator*(const Vector3& arg) const;
	Vector3 operator*(double arg) const;
	Vector3 & operator*=(const Matrix3 & rhs);
	Vector3 & operator*=(const Matrix4 & rhs);
};

struct Rotations : Vector3
{
	// Euler angles:
	//  heading - rotation about y axis
	//  pitch - rotation about z axis
	//  bank - rotation abount x axis
	// Applied in given order
	Rotations(double heading, double pitch, double bank) : Vector3(heading, pitch, bank) {}
	Rotations(double hpb[3]) : Vector3(hpb) {}
	Rotations(float hpb[3]) : Vector3(hpb) {}
};

struct Scales : Vector3
{
	Scales(double scalex, double scaley, double scalez) : Vector3(scalex, scaley, scalez) {}
	Scales(double scalesxyz[3]) : Vector3(scalesxyz) {}
	Scales(float scalesxyz[3]) : Vector3(scalesxyz) {}
};

struct Matrix3
{
	double d[3][3];  // [row][column] notation!

	Matrix3() {}
	Matrix3(Vector3 v1, Vector3 v2, Vector3 v3); // rows or columns???
	Matrix3(
		double m11, double m12, double m13,
		double m21, double m22, double m23,
		double m31, double m32, double m33);
	Vector3 operator * (const Vector3 & v) const;
	Matrix3 operator * (const Matrix3 & v) const;
	Matrix4 operator * (const Matrix4 & v) const;
};

// matrix for transformations
//
struct Matrix4
{
	double d[4][4]; // [row][column] notation!

	Matrix4() {}
	Matrix4(
		double m11, double m12, double m13, double m14,
		double m21, double m22, double m23, double m24,
		double m31, double m32, double m33, double m34,
		double m41, double m42, double m43, double m44);
	Matrix4(Vector3 pos, Rotations rotations, Scales scales); // MUST BE APPLIED IN LightWave ORDER!!!

	Vector3 operator * (const Vector3 & rhs) const;
	Matrix4 operator * (const Matrix4 & rhs) const;
	Matrix4 operator * (const Matrix3 & rhs) const;
};

struct Ray
{
	Vector3 origin_, direction_;

	Ray(Vector3 origin, Vector3 direction);
	Vector3 getOrigin() const;
	Vector3 getDirection() const;
};

class Color
{
private:
	double r_, g_, b_;
public:
	Color(double r = 1.0, double g = 1.0, double b = 1.0);
	Color operator*(const Color& c) const;
	Color operator*(double k) const;
	Color operator+(const Color& c) const;
	double getR() const;
	double getG() const;
	double getB() const;
};


// Value object for texture coordinates
class TexCoords
{
public:
	typedef float CoordType;

	TexCoords() {}
	TexCoords(CoordType u, CoordType v) : _u(u), _v(v) {}
	TexCoords(const TexCoords& orig) : _u(orig._u), _v(orig._v) {}

	TexCoords& operator = (const TexCoords& right)
		{ _u = right._u; _v = right._v; return *this; }

	__declspec(property(get = GetU)) CoordType U;
	__declspec(property(get = GetV)) CoordType V;
	CoordType GetU() const { return _u; }
	CoordType GetV() const { return _v; }

private:
	CoordType _u, _v;
};

/*typedef vector<const Vector3> VectorList;
typedef VectorList::const_iterator VectorIter;

typedef vector<const int> TriList;
typedef TriList::const_iterator TriIter;
*/

inline Vector3 & Vector3::operator *= (const Matrix4 & rhs)
{
	return *this = rhs * (*this);
}

inline Matrix3::Matrix3(
	double m11, double m12, double m13,
	double m21, double m22, double m23,
	double m31, double m32, double m33)
{
	d[0][0] = m11, d[0][1] = m12, d[0][2] = m13;
	d[1][0] = m21, d[1][1] = m22, d[1][2] = m23;
	d[2][0] = m31, d[2][1] = m32, d[2][2] = m33;
}

inline Matrix4::Matrix4(
	double m11, double m12, double m13, double m14,
	double m21, double m22, double m23, double m24,
	double m31, double m32, double m33, double m34,
	double m41, double m42, double m43, double m44)
{
	d[0][0] = m11, d[0][1] = m12, d[0][2] = m13, d[0][3] = m14;
	d[1][0] = m21, d[1][1] = m22, d[1][2] = m23, d[1][3] = m24;
	d[2][0] = m31, d[2][1] = m32, d[2][2] = m33, d[2][3] = m34;
	d[3][0] = m41, d[3][1] = m42, d[3][2] = m43, d[3][3] = m44;
}


inline Matrix4::Matrix4(Vector3 pos, Rotations rotations, Scales scales)
{
	*this = translateMatrix(pos) * rotationMatrix(rotations) * scaleMatrix(scales);
}

inline Vector3 Matrix4::operator * (const Vector3 & rhs) const
{
	Vector3 result;
	for (int i = 0; i < 3; i++)
	{
		result.xyz[i] = d[0][i] * rhs.xyz[0] + d[1][i] * rhs.xyz[1] + d[2][i] * rhs.xyz[2] + d[3][i];
	}
	return result;
}

inline Matrix4 Matrix4::operator * (const Matrix4 & rhs) const
{
	Matrix4 result;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			result.d[i][j] = 0;
			for (int k = 0; k < 4; k++)
			{
				result.d[i][j] = d[i][k] * rhs.d[k][j];
			}
		}
	}
	return result;
}

// extending right matrix with 1 row and col, which consists of 0-s and 1 at [4][4] pos.
inline Matrix4 Matrix4::operator * (const Matrix3 & rhs) const
{
	Matrix4 result;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			result.d[i][j] = 0;
			for (int k = 0; k < 4; k++)
			{
				double rhsEl;
				if (k == 4 || j == 4)
				{
					if (k == j)
					{
						rhsEl = 1;
					}
					else
					{
						rhsEl = 0;
					}
				}
				else
				{
					rhsEl = rhs.d[k][j];
				}
				result.d[i][k] = d[i][k] * rhsEl;
			}
		}
	}
	return result;
}

inline Matrix4 Matrix3::operator * (const Matrix4 & rhs) const
{
	Matrix4 result;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			result.d[i][j] = 0;
			for (int k = 0; k < 4; k++)
			{
				double lhsEl;
				if (i == 4 || k == 4)
				{
					if (i == k)
					{
						lhsEl = 1;
					}
					else
					{
						lhsEl = 0;
					}
				}
				else
				{
					lhsEl = rhs.d[i][k];
				}
				result.d[i][j] = lhsEl * d[k][j];
			}
		}
	}
	return result;
}

inline Matrix4 translateMatrix(const Vector3 & translates)
{
	return Matrix4(
		1, 0, 0, translates.x,
		0, 1, 0, translates.y,
		0, 0, 1, translates.z,
		0, 0, 0, 1);
}

inline Matrix4 translateMatrix(double x, double y, double z)
{
	translateMatrix(Vector3(x, y, z));
}

inline Matrix4 translateMatrix(const double xyz[3])
{
	translateMatrix(Vector3(xyz));
}

inline Matrix3 scaleMatrix(const Vector3 & scales)
{
	return Matrix3(
		scales.x,        0,        0,
		       0, scales.y,        0,
		       0,        0, scales.z);
}

inline Matrix3 scaleMatrix(double x, double y, double z)
{
	return scaleMatrix(Vector3(x, y, z));
}

inline Matrix3 scaleMatrix(const double xyz[3])
{
	return scaleMatrix(Vector3());
}

inline Matrix3 headingMatrix(double a) // rotation about y axis
{
	return Matrix3(
		cos(a), 0, -sin(a),
		     0, 1,       0,
		sin(a), 0,  cos(a)
		);
}

inline Matrix3 pitchMatrix(double a) // rotation about z axis
{
	return Matrix3(
		 cos(a), sin(a), 0,
		-sin(a), cos(a), 0,
		      0,      0, 1
		);
}

inline Matrix3 bankMatrix(double a) // rotation about x axis
{
	return Matrix3(
		1,       0,       0,
		0,  cos(a),  sin(a),
		0, -sin(a),  cos(a)
		);
}

inline Matrix3 rotationMatrix(Vector3 hpb)
{
	return headingMatrix(hpb.x) * pitchMatrix(hpb.y) * bankMatrix(hpb.z);
}

inline Matrix3 rotationMatrix(double heading, double pitch, double bank)
{
	return rotationMatrix(Vector3(heading, pitch, bank));
}

inline Matrix3 rotationMatrix(const double hpb[3])
{
	return rotationMatrix(Vector3(hpb[0], hpb[1], hpb[2]));
}

class BBox
{
private:
	Vector3 min_, max_;
public:
	BBox(const BBox & box);
	BBox(Vector3 v);

	BBox operator+(const Vector3& v);
};

#endif //TYPES_HPP_INCLUDE